package org.jmap.geom;

import java.util.List;

import org.jmap.core.Extent;
import org.jmap.core.MapPoint;
import org.jmap.core.Util;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.osgeo.proj4j.CoordinateTransform;
import org.osgeo.proj4j.CoordinateTransformFactory;
import org.osgeo.proj4j.ProjCoordinate;

/**
 * a 2D Point implementation of the <code>Geometry</code> interface
 * 
 * @author Ali
 *
 */
public class Point extends AbstractGeometry {

	public double x, y;
	
	public Point() {
		super();
	}
	
	public Point(double x, double y) {
		super();
		
		this.x = x;
		this.y = y;
	}
	
	@Override
	public void computeExtent() {
		this.setExtent(new Extent(x, y, x, y));
	}

	@Override
	public double distanceTo(Geometry geometry, boolean edge) {
		double x0, x1, y0, y1, distance;

		if (geometry instanceof Point) {
			x0 = this.x;
			y0 = this.y;
			x1 = ((Point) geometry).x;
			y1 = ((Point) geometry).y;
			distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
			
		} else {
			//delegate to other geometry distanceTo method
			distance = geometry.distanceTo(this, edge);
		}
		
		return distance;
	}
	
	/**
	 * moves this Point by an arbitrary x and y values
	 * 
	 * @param x the amount of units in x to move to
	 * @param y the amount of units in y to move to
	 */
	public void translate(double x, double y) {
		this.x += x;
		this.y += y;
		
		this.clearExtent();
	}
	
	/**
	 * rotates this point around the given origin to an arbitrary angle in degrees
	 * 
	 * @param angle the angle to rotate in degrees
	 * @param origin the origin of rotation
	 */
	public void rotate(double angle, Point origin) {
		angle *= Math.PI / 180;
		double radius = this.distanceTo(origin, false);
		double theta = Math.atan2(this.y - origin.y, this.x - origin.x);
		this.x = theta + (radius * Math.cos(theta));
		this.y = theta + (radius * Math.sin(theta));
		
		this.clearExtent();
	}

	
	
	@Override
	public Point getCentroid() {
		return new Point(this.x, this.y);
	}
	
	/**
	 * resizes a <code>Point</code> geometry relative to another <code>Point</code>,
	 * this has the effect of scaling a vector from the origin to the Point
	 * 
	 * @param scale Ratio of the new distance to the origin to the old distance to the origin
	 * @param origin the <code>Point</code> of origin
	 * @param ratio the x:y ratio for the resize
	 * @return the current resized geometry Point
	 */
	public Geometry resize(double scale, Point origin, double ratio) {        
        this.x = origin.x + (scale * ratio * (this.x - origin.x));
        this.y = origin.y + (scale * (this.y - origin.y));
        this.clearExtent();
        
        return this;
	}
	
	public boolean intersects(Geometry geometry) {
		boolean intersects = false;
		if (geometry instanceof Point) {
			intersects = this.equals(geometry);
		} else {
			intersects = geometry.intersects(this);
		}
		
		return intersects;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(x);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		temp = Double.doubleToLongBits(y);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Point))
			return false;
		Point other = (Point) obj;
		if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
			return false;
		if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
			return false;
		return true;
	}
	
	@Override
	public Geometry transform(CoordinateReferenceSystem sourceCRS, CoordinateReferenceSystem targetCRS) {
		CoordinateTransformFactory transformFactory = new CoordinateTransformFactory();
		CoordinateTransform transform = transformFactory.createTransform(sourceCRS, targetCRS);
		
		ProjCoordinate result = new ProjCoordinate();
		transform.transform(new ProjCoordinate(this.x, this.y), result);
		
		return new Point(result.x, result.y);
	}
	
	public MapPoint toMapPoint() {
		return new MapPoint(this.x, this.y);
	}

	@Override
	public String toString() {
		return String.format("Point [x=%s, y=%s]", x, y);
	}

	@Override
	public boolean intersects(Geometry geometry, boolean edge) {
		boolean intersects = false;
		intersects = this.equals(geometry);
		
		return intersects;
	}

	@Override
	public double getLength() {
		return 0;
	}

	@Override
	public double getArea() {
		return 0;
	}

	@Override
	public double getGeodesicLength(CoordinateReferenceSystem projection) {
		return 0;
	}

	@Override
	public double geodesicDistanceTo(Geometry geometry, boolean edge) {
		return 0;
	}

	@Override
	public double getGeodesicArea(CoordinateReferenceSystem projection) {
		return 0;
	}

	@Override
	public void scale(double scaleFactor, Point origin, double ratio) {
        this.x = origin.x + (scaleFactor * ratio * (this.x - origin.x));
        this.y = origin.y + (scaleFactor * (this.y - origin.y));
        
        this.clearExtent();

	}

	
}
